/* Generated by Interface Builder */

#import "ClassManager.h"
#import "MenuManager.h"
#import "NuString.h"
#import "PrefsManager.h"
#import "FinderManager.h"
#import <appkit/Application.h>
#import <appkit/Text.h>
#import <appkit/Panel.h>
#import <appkit/NXBrowser.h>
#import <appkit/NXBrowserCell.h>
#import <defaults/defaults.h>
#import <appkit/NXSplitView.h>
#import <appkit/Matrix.h>
#import <objc/objc-load.h> 
// #import <objc/objc-runtime.h> // causes compiler errors in 3.0!
#import <objc/Storage.h>
#import <objc/List.h>
#import <streams/streams.h>
#import <libc.h>
#import <strings.h>
#include <sys/types.h>
#include <sys/stat.h>
#import "GlyphView.h"
#import <regex.h>

extern id Nu ;
extern  void NuGets(NXStream *aStream, char * aBuf) ;
static id loadList ; // this global var points to a  list of
                     // NuStrings containing
		     // the classnames of every loaded class

id  classWinList ;  // this global var contains a list of the id's of all
                    // currently open class windows


@implementation ClassManager: WorkspaceManager

int superClassName(char *filePath, char *superClass) 
{ // assumes filePath is the path to a .o file
  // containing a class definition. Sets superClass
  // to the name of its superclass. Returns 1 if
  // successful, else 0.
  struct mach_header mh ;
  struct segment_command sc ;
  struct section sect ;
  struct objc_class oc ;
  int fn, i,j ;
  char c ;
  if((fn = open(filePath, O_RDONLY)) < 0)
    return 0 ;
  lseek(fn, 0l, 0) ;
  if(read(fn,&mh,sizeof(struct mach_header)) < sizeof(struct mach_header))
    return 0  ; // short file
  for(i = 0 ; i < mh.ncmds ; i++) // for each load command
  { if(read(fn,&sc,sizeof(struct load_command)) < sizeof(struct load_command))
       return 0 ; // short file
    if(sc.cmd != LC_SEGMENT) // not a segment command, skip it
      if(lseek(fn, sc.cmdsize - sizeof(struct load_command) ,L_INCR) == -1)
        return 0 ; // can't seek
    else // segment command...get rest of header
    { if(read(fn,&sc.segname,sizeof(struct segment_command) - 
           sizeof(struct load_command)) < sizeof(struct segment_command) - 
           sizeof(struct load_command))
         return 0 ; // short file
      else
      { for(j = 0 ; j < sc.nsects ; j++) // for each segment header
        { if(read(fn,&sect,sizeof(struct section)) < sizeof(struct section))
            return 0 ; // short file ;
          if(!strcmp(sect.segname,"__OBJC") && 
              !strcmp(sect.sectname,"__class"))
          { // we've found the correct section and segment
            if(lseek(fn,sect.offset,L_SET) == -1)
              return 0 ; // can't seek
            if(read(fn,&oc,sizeof(struct objc_class))
                < sizeof(struct objc_class))
              return 0 ; // short file
	    if(lseek(fn,mh.sizeofcmds  + (long int) oc.super_class +
                sizeof(struct mach_header), L_SET) == -1)
               return 0 ; // can't seek
            i = 0 ;
            while(read(fn,&c,1))
            { superClass[i] =  c ;
              if(!superClass[i++])
              { close(fn) ;
                return 1 ;
              }
            }
          }
        }
      }
    }
  }
  close(fn) ;
  return 0 ;
}

+ compileClass:(char *) aClass file:(char *) aFile
{ // compile file aFile containing .m file for
  // class aClass.  return nil on error.
  char buf[512] ;
  const char *rootPath ;
  char tmpFile[20] = "NuXXXXXX" ;
  int rval ;
  // use awk to create .h file
  mktemp(tmpFile) ;
  rootPath = NXGetDefaultValue([NXApp appName],"NuPath") ; 
  sprintf(buf,
  "awk '\\\n"
  "/^#pragma .h/      { $1 = \"\";$2=\"\"; print}\\\n"
  "/^@implementation/ { if(state == 0) {$1 = \"@interface\"; print ;state++} }\\\n"
  "/^{/               { if(state == 1) state++ }\\\n"
  "                  { if(state == 2) print }\\\n"
  "/^}/               { if(state == 2) state++ }\\\n"
  "/^[+-]/           { if(state == 3) print }\\\n"
  "/^@end/	   { print \"@end\" ; state = 0}'\\\n"
   "  < %s > %s/.h/%s.h",
    aFile, rootPath, aClass ) ;
  system(buf) ;
  // compile into .o file ... don't remove the -c !
  sprintf(buf, "cc -c -I %s/.h  %s -o %s/.o/%s.o %s"
     "  2> /tmp/%s",
      rootPath,
      NXGetDefaultValue([NXApp appName],"CompilerOptions"),
     rootPath, aClass, aFile, tmpFile) ;
  if(rval = system(buf)) // non-zero exit status == errors
  { sprintf(buf,"/tmp/%s",tmpFile) ;
    [Nu appendFileToTranscript: buf] ;
    NXRunAlertPanel("Nu","Compilation errors: see Transcript Window\n",
         NULL,NULL,NULL) ;
  } 
  sprintf(buf,"rm /tmp/%s 2> /dev/null",tmpFile) ;
  system(buf) ;
  if(rval == 0)
    return self ;
  else
    return nil ;
}


+ initialize ;
// initialize the loadList
{ loadList = [List new] ;
  classWinList = [List new] ;
  return [super initialize] ;
}

+(BOOL) isLoaded: (char *) aClass ;
// returns YES iff aClass is currently loaded.
{ NXHashTable *class_hash = objc_getClasses();
  NXHashState state = NXInitHashState(class_hash);
  Class class;
  while(NXNextHashState(class_hash, &state, (void **) &class))
  { if(!strcmp(class->name,aClass))
      return YES ; 
  }
  return NO ;
}

+ loadAll ;
// loads all the classes on the loadList.  This is
// called when unarchiving: first the loadList is
// established from the archive, then this method
// is invoked to load all these classes.  Returns
// nil and unloads all classes if any class cannot
// be loaded.  Does not rebuild debug files
{ int i, knt, moduleKnt ;
  char buf[512] ;
  id linkList ;
  if(!loadList)  // loadList not initialized == nothing to load
    return nil ; 
  knt = [loadList count] ;
  linkList = [NXGetNamedObject("PrefsManager",NXApp) linkList] ;
  moduleKnt = [linkList count] ;
  { char *moduleNames[moduleKnt+2] ;
    for(i = 0 ; i < moduleKnt ; i++) // make list of libraries
      moduleNames[i+1] = (char *) [[linkList objectAt: i] cString] ;
    moduleNames[i+1] = NULL ; // terminate the list
    for(i = 0 ; i < knt ; i++)
    { sprintf(buf,"%s/%s.o",NXGetDefaultValue([NXApp appName],"NuPath"),
            [[loadList objectAt: i] cString]) ;
      moduleNames[0] = buf ;
      if(objc_loadModules(moduleNames, NULL, NULL, NULL,NULL))
      { NXRunAlertPanel("Nu","Error in loading, no classes loaded",
           NULL,NULL,NULL) ;
        [self unloadAll] ;
        return self ;
      }
    }
  }
  return self ;
}

+(int) loadIndex: (const char *) nameOfClass ;
// if nameOfClass is in the load list, return its index,
// else return -1.  The load list is a list of
// all classes which have been dynamically loaded.
{ int i, knt ;
  knt = [loadList count] ;
  for(i = 0 ; i < knt ; i++)
  { if(!strcmp([[loadList objectAt: i] cString], (char *) nameOfClass))
      return i ;
  }
  return -1 ;
}

+ loadList ;
{ return loadList ;
}

+ loadList: aList ;
{ loadList = aList ;
  return self ;
}

+ load: (const char *) aClass ;
{ // returns nil if aClass can't be loaded
  int i, loadIndex, knt ;
  id nmList, oldIdList, newIdList, gVList ;
  char *ptr ;
  id placeHolder, rval ;
  rval = self ;
  nmList = [[List alloc] init] ;
  oldIdList = [[List alloc] init] ;
  newIdList = [[List alloc] init] ;
  gVList = [[List alloc] init] ;
  placeHolder = [[Object alloc] init] ;
  if((loadIndex = [self loadIndex: aClass]) == -1)
  { // not loaded ;
    [oldIdList addObject: placeHolder] ; // don't know id yet...
    [nmList addObject: [NuString new: (char *) aClass]] ;
  }
  else
  { // must unload it, and classes loaded 'after' it
    knt = [loadList count] ;
    for(i = loadIndex ; i < knt ; i++)
    { ptr = (char *) [[loadList objectAt: i] cString] ;
      [oldIdList addObject:objc_getClass(ptr)] ;
      [nmList addObject:[NuString new: ptr]] ;
    }
    for(i = loadIndex ; i < knt ; i++)
    { objc_unloadModules(NULL,NULL) ;
      [[loadList removeLastObject] free] ; 
    }
  }
  knt = [oldIdList count] ;
  for(i = 0 ; i < knt ; i++)
  { ptr = (char *) [[nmList objectAt: i] cString] ;
    if(![self loadAux: ptr])
    { NXRunAlertPanel("Nu","Can't load: %s\n"
	"See Transcript for details.", NULL,NULL,NULL,ptr) ;
      rval = nil ; 
    }
    else
       [newIdList addObject: objc_getClass(ptr)] ;
  }
  [GlyphView become:newIdList if:oldIdList views: gVList] ;
  [gVList makeObjectsPerform: @selector(display)] ;
  [nmList makeObjectsPerform: @selector(free)] ;
  [nmList free] ;
  [oldIdList free] ;
  [newIdList free] ;
  [gVList free] ;
  [placeHolder free] ;
  [[Nu loadedClassesBrowser] loadColumnZero] ;
  [[[Nu loadedClassesBrowser] matrixInColumn: 0]
      scrollCellToVisible:[loadList count] - 1 :0] ;
  return rval ;
}

+ (BOOL) loadAux:  (char *) aClass  ;
{ // try to load aClass. Return YES iff successful,
  // else return NO.
  const char *baseFile ;
  char file[512], tempFile[512], superClass[256], *debugFile ;
  const char *theFlags ;
  NXStream *aStream ;
  int i, moduleKnt ;
  id linkList ;
  // see if class is "hard loaded"
  if([self isLoaded: aClass])
    return YES ;
  aStream = NXOpenMemory(NULL, 0, NX_READWRITE) ;
  baseFile = NXGetDefaultValue([NXApp appName],"NuPath") ;
  sprintf(file,"%s/.o/%s.o",baseFile,aClass);
  if(!superClassName(file,superClass)) 
  { [Nu printf: "Corrupt file: %s. Try recompiling it.\n", aClass] ;
    return NO ;
  }
  if([self loadIndex: superClass] == -1)
  { // superClass not loaded
    if(![self loadAux: superClass])
    { [Nu printf: "Couldn't load class %s, superclass of %s.\n",
          superClass, aClass] ;
      return NO ;
    }
  }
  linkList = [NXGetNamedObject("PrefsManager",NXApp) linkList] ;
  moduleKnt = [linkList count] ;
  theFlags = NXGetDefaultValue([NXApp appName],"Flags") ;
  if(theFlags[DEBUGFILES] - '0')
  { sprintf(tempFile,"%s/.g/%s.g",baseFile,aClass) ;
    debugFile = tempFile ;
  }
  else
    debugFile = NULL ;
  { char *modules[moduleKnt + 2] ;
    for(i = 0 ; i < moduleKnt ; i++)
      modules[i+1] = (char *) [[linkList objectAt: i] cString] ;
    modules[0] = file ;
    modules[i+1] = NULL ;
    if(!objc_loadModules(modules, aStream, NULL, NULL,debugFile))
    { [loadList addObject: [NuString new: aClass]] ;
      NXCloseMemory(aStream,NX_TRUNCATEBUFFER) ;
      return YES ;
    }
    else // load failed
    { char *textBuf ;
      int textLen, maxLen ;
      NXGetMemoryBuffer(aStream, &textBuf, &textLen, &maxLen);
      [Nu printf: "%s\n", textBuf] ;
      NXCloseMemory(aStream,NX_TRUNCATEBUFFER) ;
      return NO ;
    }
  }
}


+ unload: (const char *) aClass ;
{ // unload aClass if it was incrementally loaded.
  // returns nil if unable. Disallow unloading
  // the "system" classes "Root","Glyph", "Tray",
  // and "Error".
  volatile List *nmList,*oldIdList, *newIdList, *gVList ;
  volatile char *ptr ;
  volatile id placeHolder, rval, rG ;
  volatile int i , ind , knt  ;
  volatile NXRect aRect ;
  if(!strcmp(aClass,"Glyph") || !strcmp(aClass,"Root") 
     || !strcmp(aClass,"Tray") || !strcmp(aClass,"Error"))
  { NXRunAlertPanel("Nu","Error: you cannot unload class %s,\n"
     "though you may edit, recompile, and reload it.",
      NULL,NULL,NULL,aClass) ;
    return self ;
  }
  rval = self ;
  nmList = [List new] ;
  oldIdList = [List new] ;
  newIdList = [List new] ;
  gVList = [List new] ;
  placeHolder = [Object new] ;
  ind = [self loadIndex: aClass] ;
  if(ind == -1)
    return nil ;
  else
  { // must unload it, and classes loaded 'after' it
    knt = [loadList count] ;
    for(i = ind  ; i < knt ; i++)
    { [oldIdList addObject: objc_getClass((char *)[[loadList objectAt: i] cString])] ;
      if(i == ind) // Will change existing aClass instances into Error glyphs
        [nmList addObject: [NuString new: "Error"]] ;
      else
        [nmList addObject: [NuString new: (char *) [[loadList objectAt: i] cString]]] ;
    }
    for(i = ind ; i < knt ; i++)
    { objc_unloadModules(NULL,NULL) ;
      [[loadList removeLastObject] free] ; 
    }
  }
  knt = [oldIdList count] ;
  for(i = knt - 1 ; i >= 0 ; i--)
  { ptr = (char *) [[nmList objectAt: i] cString] ;
    if(![self loadAux: ptr])
    { NXRunAlertPanel("Nu","Can't load: %s\n"
	"See Transcript for details.", NULL,NULL,NULL,ptr) ;
      rval = nil ; 
    }
    else
      [newIdList insertObject: objc_getClass((char *) [[nmList objectAt: i] cString]) at: 0] ;
  }
  [GlyphView become:newIdList if:oldIdList views: gVList] ;
  // redisplay all glyphViews which have glyph classes which have changed
  knt = [gVList count] ;
  for(i = 0 ; i < knt ; i++)
  { rG = [[gVList objectAt: i] rootGlyph] ;
    [rG getFrame: &aRect] ;
    [rG display: &aRect from: nil] ;
  }
  [gVList makeObjectsPerform: @selector(display)] ;
  [gVList free] ;
  [nmList makeObjectsPerform: @selector(free)] ; 
  [nmList free] ;
  [oldIdList free] ;
  [placeHolder free] ;
  [[Nu loadedClassesBrowser] loadColumnZero] ;
  return rval ;
}

+ unloadAll ;
// unload all incrementally loaded classes, except the
// "system" classes, which will always be the bottom 4.
{ int i, loadKnt ;
  loadKnt = [loadList count] ;
  for(i = 0 ; i < loadKnt - 4 ; i++)
  { [[loadList lastObject] free] ;
    [loadList removeLastObject] ;
    objc_unloadModules(NULL,NULL) ;
  }
  [[Nu loadedClassesBrowser] loadColumnZero] ;
  return self ;  
}

+winList ;
{ return classWinList ;
}


- (int)browser:sender fillMatrix:matrix inColumn:(int)column ;
{ if(msgList)
    return [msgList count] ;
  else
    return 0 ;
}

- browser:sender loadCell:cell atRow:(int)row inColumn:(int)column ;
{ [cell setStringValue: [[msgList objectAt: row] cString]] ;
  [cell setLeaf: YES] ;
  return self ;
}


- className: (char *) name ;
{ // copy className into ivar
  strncpy(className, name, 63) ;
  return self ;
}
 

- classText: (char *) theText ;
{ // copy theText into text object
  [textView setText: theText] ;
  [self setDocEdited:YES];
  return self ;
}

- compile:sender ;
{ // returns nil if any errors
  id rval ;
  if([self isDocEdited]) // be sure file is saved first
    [self save: self] ;
  [self message: "Compiling..."] ;
  rval = [ClassManager compileClass: className file: fileName] ;
  [self message: ""] ;
  return rval ;
}


- editSuperClass: sender ;
{ // attempt to open this class's superclass definition
  char superClass[256] ;
  char path[2048] ;
  struct stat statBuf ;
  // can't edit superclass of class Glyph!
  if(!strcmp(className,"Glyph"))
  {  NXRunAlertPanel("Nu",
      "Error: can't edit class Object,\n"
      "superclass of class Glyph\n",
       NULL,NULL,NULL) ;
     return nil ;
  }
  // create path to my .o file
  sprintf(path,"%s/.o/%s.o",
    NXGetDefaultValue([NXApp appName],"NuPath"),className) ;
  // verify the path
  if(stat(path,&statBuf))
  {  NXRunAlertPanel("Nu",
      "Error: can't find binary for class %s\n"
      "Have you compiled it?",
      NULL,NULL,NULL,className) ;
     return nil ;
  }
  // get the superclass's name
  if(!superClassName(path, &superClass[0]))
  {  NXRunAlertPanel("Nu",
      "Error: can't determine superclass of %s\n"
      "Try recompiling %s",
      NULL,NULL,NULL,className, className) ;
     return nil ;
  }
  // (attempt to) edit the superclass
  if(![[Nu glyphManager] editFile: nil class: superClass])
  {  NXRunAlertPanel("Nu",
      "Error: can't edit class %s,\n"
      "superclass of %s.  Is it compiled?",
      NULL,NULL,NULL,className, superClass) ;
     return nil ;
  }
  return self ;
}

- (char *) extension ;
{ // provide file extension
  return "m" ;
}

- format: sender ;
{ // format my text and update the browser
  char textBuf[256], quotedBuf[513] ;
  char *c, *d, fontStuff[5][5][40], defName[12] ;
  const char *defaults ;
  int i, len, charNum ; 
  NXStream *rawStream, *cookedStream ;
  [msgList freeObjects] ;
  rawStream = [textView stream] ;
  cookedStream = NXOpenMemory(NULL,0,NX_READWRITE) ;
  NXPrintf(cookedStream,
       "{\\rtf0\\ansi{\\fonttbl") ;
  // get the font information from defaults database
  for(i = 0 ; i < 5 ; i++)
  { // Manufacture default name 
    sprintf(defName,"classText%1d",i) ;
    defaults = NXGetDefaultValue([NXApp appName],defName) ;
    // parse the default value
    sscanf(defaults,"%s %s %s %s %s %s",
       textBuf, fontStuff[i][0], fontStuff[i][1],
       fontStuff[i][2],fontStuff[i][3], fontStuff[i][4]) ;
    NXPrintf(cookedStream,"\\f%1d\\fnil %s;", i,textBuf) ;
 }
 NXPrintf(cookedStream,"}\n") ;
 NXPrintf(cookedStream,"{\\colortbl") ;
 for(i = 0 ; i < 5 ; i++)
 { NXPrintf(cookedStream,fontStuff[i][1]) ;
   NXPrintf(cookedStream,";") ;
 }
 NXPrintf(cookedStream,"}\n") ;

 charNum = 0 ;
 do
 { NXSeek(rawStream, charNum, NX_FROMSTART) ;
   NuGets(rawStream,textBuf) ;
   for(c = textBuf, d = quotedBuf ; *c ;)
   { if(*c == '{' || *c == '}' || *c == '\\' || *c == '-' || *c == '\n')
     { // must quote these chars for rtf
        *d++ = '\\' ;
     }
     if(*c == '/' && *(c+1) == '/')
     { char tempStr[256] ;
        // change style for slash-slash comment
        sprintf(tempStr,"\\f3%s\\cf3%s%s%s",
          fontStuff[3][0],fontStuff[3][2],
         fontStuff[3][3], fontStuff[3][4]) ;
        strcpy(d,tempStr) ;
        (int) d += strlen(tempStr) ;
      }
      *d++ = *c++ ;
    }
    *d = '\0' ;
    switch(textBuf[0])
    { case '#': i = 0 ; break ;
      case '@': i = 1 ; break ;
      case '/':
      case '-':
      case '+': i = 2 ; break ;
      default : i = 4 ; break ;
     }
     NXPrintf(cookedStream,
       "\\f%1d%s\\cf%1d%s%s%s %s", i,
        fontStuff[i][0], i,fontStuff[i][2],
         fontStuff[i][3],fontStuff[i][4],quotedBuf) ;
     if(i == 1 || i == 2) // add to msgList
       [msgList addObject: [NuString new: textBuf]] ;
    len = strlen(textBuf) ;
    charNum += len ; // don't forget the newline
  } while(!NXAtEOS(rawStream)) ;
  NXSeek(cookedStream, 0, NX_FROMSTART) ;
  /* uncomment to print stream to transcript 
  { char *a ;
    int t,m ;
    NXGetMemoryBuffer(cookedStream, &a, &t, &m);
    [Nu printf: a] ;
  }
  */
  [textView readRichText: cookedStream] ;
  NXCloseMemory(cookedStream,NX_TRUNCATEBUFFER) ;
  [msgBrowser loadColumnZero] ;
  return self ;
}

- free ;
{ [msgList freeObjects] ;
  [msgList free] ;
  return [super free] ;
}

- goToMsg: sender ;
{ // sender is a cell in the msgBrowser ; find
  // the text in the textView
  id manager ;
  manager = NXGetNamedObject("FinderManager",NXApp) ;
  if(manager) // should always be true
  { [manager find: (char *) 
       [[[sender matrixInColumn: 0] selectedCell]stringValue]
    regEx: NO caseSensitive: YES inSelection: NO] ;
  }
  return self ;
}

- init ;
{ // add my id to the classWinList 
  [super init] ;
  [classWinList addObject: self] ;
  // assemble the pieces we get from the nib
  [msgBrowser useScrollButtons: YES] ;
  [splitView addSubview: [msgBrowser removeFromSuperview]] ;
  [splitView addSubview: [[[textView superview] superview]
      removeFromSuperview]] ;
  // make sure we are not monofont
  [self setMiniwindowIcon: "cmIcon.tiff"] ;
  // create Storage for line numbers of
  // lines in the msgBrowser
  msgList = [List new] ;  
  return self ;
}


- load:sender ;
{ [self message: "Loading..."] ;
  [ClassManager load:className] ;
  [self message: ""] ;
  return self ;
}

- (const char *) name ;
{ // redefine so that I can easily
  // be identified
  return className ;
}

- readFile ;
{ NXStream *fileStream ;
  if(fileName[0] == '\0')
    return self ; // no file to read!
  [self message: "Reading file..."] ;
  if((fileStream = NXMapFile(fileName,NX_READONLY)) != NULL)
  { [textView readText: fileStream] ;
    NXCloseMemory(fileStream,NX_FREEBUFFER);
    [self format: self] ; 
    [self setDocEdited: NO] ;
  }
  else
    NXRunAlertPanel("Nu", "Error, couldn't read: %s",
        NULL,NULL,NULL,fileName) ;
  [self message: ""] ;
  return self ;
}


-(BOOL) saveTextToFileName ;
// pre:  -ivar fileName contains a valid
//       file pathname.
//       -ivar textView contains a TextView object
// post: if file can be opened or created, with mode
//       644: text of textView is written out to the
//       file named by fileName; file is closed, and
//       YES is returned.
//       otherwise returns NO
{ NXStream *aStream ;
  [self message: "Saving file..."] ;
  aStream = NXOpenMemory(NULL,0, NX_WRITEONLY);	
  [textView writeText:aStream];	
  NXSaveToFile(aStream, fileName);
  NXCloseMemory(aStream, NX_FREEBUFFER);	
  [self setDocEdited:NO];
  [self message: ""] ;
  return YES ;
}

- update: sender ;
{ // save if needed, compile if needed, load if needed
  struct stat mStat,oStat ;
  const char *rootPath ;
  char comBuf[512] ;
  rootPath = NXGetDefaultValue([NXApp appName],"NuPath") ; 
  if([self isDocEdited]) // be sure file is saved first
    [self save: self] ;
  stat(fileName,&mStat) ;
  sprintf(comBuf,"%s/.o/%s.o",rootPath,className) ;
  if(stat(comBuf,&oStat) || (mStat.st_mtime >= oStat.st_mtime)) 
  { // no .o file or .o out of date 
   if([self compile: self]) 
     [self load: self] ; 
  }
  else if([ClassManager loadIndex: className] == -1)
    [self load: self] ;
  return self ;    
}

- windowWillClose: sender ;
{ // remove myself from the classWinList
  [classWinList removeObject: self] ;
  return self ;
}

- read: (NXTypedStream *) aStream ;
{ [super read: aStream] ;
  NXReadArray(aStream, "c", 64, className) ;
  return self ;
}

- write: (NXTypedStream *) aStream ;
{ [super write: aStream ] ;
  NXWriteArray(aStream, "c", 64, className) ;
  return self ;
}

@end
